//
// Created by EmFi on 2023/5/18.
//

#include <algorithm>
#include "IndexedDatabase.h"

IndexedDatabase::IndexedDatabase(const string &fn) : Database(fn), xIndex(2), yIndex(2), zIndex(2), wIndex(2) {
    xIndex.open(fn + ".0.index");
    yIndex.open(fn + ".1.index");
    zIndex.open(fn + ".2.index");
    wIndex.open(fn + ".3.index");
    LogUtil::debug(this->fn, "Index opened");
    if (this->head.length > 0) {
        xIndex.read();
        yIndex.read();
        zIndex.read();
        wIndex.read();
    }
}

IndexedDatabase::~IndexedDatabase() {
    LogUtil::debug(this->fn, "Index closed");
}

int IndexedDatabase::_insert(Point point) {
    int index = Database::_insert(point);
    xIndex.insert(point.getX(), index);
    yIndex.insert(point.getY(), index);
    zIndex.insert(point.getZ(), index);
    wIndex.insert(point.getW(), index);
    this->save();
    return index;
}

int IndexedDatabase::insert(Point point) {
    if (xIndex.contains(point.getX()) || yIndex.contains(point.getY()) || zIndex.contains(point.getZ()) || wIndex.contains(point.getW())) {
        LogUtil::error(this->fn, "Duplicated record");
        return -1;
    }
    int index = Database::insert(point);
    xIndex.insert(point.getX(), index);
    yIndex.insert(point.getY(), index);
    zIndex.insert(point.getZ(), index);
    wIndex.insert(point.getW(), index);
    this->save();
    return index;
}

int IndexedDatabase::_insert(list<Point> list) {
    int startIndex = Database::_insert(list);
    int index = 0;
    for (const auto& it : list) {
        xIndex.insert(it.getX(), startIndex + index);
        yIndex.insert(it.getY(), startIndex + index);
        zIndex.insert(it.getZ(), startIndex + index);
        wIndex.insert(it.getW(), startIndex + index);
        index++;
    }
    this->save();
    return startIndex;
}

int IndexedDatabase::insert(list<Point> list) {
    for (const auto& it : list) {
        if (xIndex.contains(it.getX()) || yIndex.contains(it.getY()) || zIndex.contains(it.getZ()) ||
            wIndex.contains(it.getW())) {
            stringstream ss;
            ss << "Duplicated object: " << it;
            LogUtil::error(this->fn, ss.str());
            return -1;
        }
    }
    int startIndex = Database::insert(list);
    int index = 0;
    for (const auto& it : list) {
        xIndex.insert(it.getX(), startIndex + index);
        yIndex.insert(it.getY(), startIndex + index);
        zIndex.insert(it.getZ(), startIndex + index);
        wIndex.insert(it.getW(), startIndex + index);
        index++;
    }
    this->save();
    return startIndex;
}

Point IndexedDatabase::_remove(int index) {
    Point removed = Database::_remove(index);
    if (!removed.isNull()) {
        xIndex.remove(removed.getX());
        yIndex.remove(removed.getY());
        zIndex.remove(removed.getZ());
        wIndex.remove(removed.getW());
        this->save();
    }
    return removed;
}

Point IndexedDatabase::remove(int index) {
    Point removed = Database::remove(index);
    if (!removed.isNull()) {
        xIndex.remove(removed.getX());
        yIndex.remove(removed.getY());
        zIndex.remove(removed.getZ());
        wIndex.remove(removed.getW());
        this->save();
    }
    return removed;
}

Point IndexedDatabase::_update(int index, Point point) {
    Point before = Database::_update(index, point);
    if (!before.isNull()) {
        xIndex.remove(before.getX());
        yIndex.remove(before.getY());
        zIndex.remove(before.getZ());
        wIndex.remove(before.getW());
        xIndex.insert(point.getX(), index);
        yIndex.insert(point.getY(), index);
        zIndex.insert(point.getZ(), index);
        wIndex.insert(point.getW(), index);
        this->save();
    }
    return before;
}

Point IndexedDatabase::update(int index, Point point) {
    Point before = Database::update(index, point);
    if (!before.isNull()) {
        xIndex.remove(before.getX());
        yIndex.remove(before.getY());
        zIndex.remove(before.getZ());
        wIndex.remove(before.getW());
        xIndex.insert(point.getX(), index);
        yIndex.insert(point.getY(), index);
        zIndex.insert(point.getZ(), index);
        wIndex.insert(point.getW(), index);
        this->save();
    }
    return before;
}

list<Point>
IndexedDatabase::query(double xMin, double xMax, double yMin, double yMax, double zMin, double zMax, double wMin,
                       double wMax) {
    list<int> xFind = xIndex.find(xMin, xMax);
    list<int> yFind = yIndex.find(yMin, yMax);
    list<int> zFind = zIndex.find(zMin, zMax);
    list<int> wFind = wIndex.find(wMin, wMax);
    xFind.sort();
    yFind.sort();
    zFind.sort();
    wFind.sort();
    list<int> r0;
    list<int> r1;
    list<int> r2;
//    for (auto it : xFind) {
//        cout << it << " ";
//    }
//    cout << endl;
//    for (auto it : yFind) {
//        cout << it << " ";
//    }
//    cout << endl;
//    for (auto it : zFind) {
//        cout << it << " ";
//    }
//    cout << endl;
//    for (auto it : wFind) {
//        cout << it << " ";
//    }
//    cout << endl;
    set_intersection(xFind.begin(), xFind.end(), yFind.begin(), yFind.end(),inserter(r0, r0.begin()));
    set_intersection(r0.begin(), r0.end(), zFind.begin(), zFind.end(),inserter(r1, r1.begin()));
    set_intersection(r1.begin(), r1.end(), wFind.begin(), wFind.end(),inserter(r2, r2.begin()));
    list<Point> ret;
    for (auto it : r2) {
        ret.push_back(this->_select(it));
    }
    LogUtil::query(this->fn, ret.size(), ret.size());
    return ret;
}

void IndexedDatabase::save() {
    xIndex.save();
    yIndex.save();
    zIndex.save();
    wIndex.save();
}

void IndexedDatabase::optimize() {
    Database::optimize();
    xIndex.clear();
    yIndex.clear();
    zIndex.clear();
    wIndex.clear();
    int index = 0;
    for (auto it : this->_select_all()) {
        xIndex.insert(it.getX(), index);
        yIndex.insert(it.getY(), index);
        zIndex.insert(it.getZ(), index);
        wIndex.insert(it.getW(), index);
        index++;
    }
}

void IndexedDatabase::printIndex() {
    cout << "X indexes" << endl;
    xIndex.print();
    cout << "Y indexes" << endl;
    yIndex.print();
    cout << "Z indexes" << endl;
    zIndex.print();
    cout << "W indexes" << endl;
    wIndex.print();
}

list<Point>
IndexedDatabase::legacyQuery(double xMin, double xMax, double yMin, double yMax, double zMin, double zMax, double wMin,
                             double wMax) {
    return Database::query(xMin, xMax, yMin, yMax, zMin, zMax, wMin, wMax);
}

void IndexedDatabase::_clear() {
    xIndex.clear();
    yIndex.clear();
    zIndex.clear();
    wIndex.clear();
    Database::_clear();
}

void IndexedDatabase::clear() {
    xIndex.clear();
    yIndex.clear();
    zIndex.clear();
    wIndex.clear();
    Database::clear();
}
